/**
 * @file framePool.hpp
 * @author SDK_TEAM
 * @brief
 * @version 0.1
 * @date 2022-11-09
 *
 * Copyright:
 * © 2018 北京灵汐科技有限公司 版权所有。
 * 注意：以下内容均为北京灵汐科技有限公司原创，未经本公司允许，不得转载，否则将视为侵权；对于不遵守此声明或者其他违法使用以下内容者，本公司依法保留追究权。
 * © 2018 Lynxi Technologies Co., Ltd. All rights reserved.
 * NOTICE: All information contained here is, and remains the property of Lynxi.
 * This file can not be copied or distributed without the permission of Lynxi
 * Technologies Co., Ltd.
 *
 */

#pragma once

#include <lyn_api.h>
#include <semaphore.h>
#include <cassert>
#include <condition_variable>
#include <list>
#include <mutex>
#include <queue>
#include "util.hpp"

// Frame池类
/********************************************************************************
类名 : FramePool
Description:
FramePool就是Frame对象池类，iElemSize表示单个元素大小，iPoolSize表示内存池大小
Example: FramePool<> oFramePool(1000, 10)
*******************************************************************************/
class FramePool {
 public:
  FramePool(int iElemSize, int iPoolSize = 9999)
      : m_iCurPoolSize(0),
        m_iElementSize(iElemSize),
        m_iPoolSize(iPoolSize),
        m_iPopCount(0),
        m_iPushCount(0) {}

  FramePool() {}

  ~FramePool() { Release(); }

  void init(int iElemSize, int iPoolSize = 9999) {
    m_iCurPoolSize = 0;
    m_iElementSize = iElemSize;
    m_iPoolSize = iPoolSize;

    m_iPopCount = 0;
    m_iPushCount = 0;
  }

  // 获取对象
  lynCodecBuf_t *Pop() {
    std::unique_lock<std::mutex> lock(m_oMutex);

    while (m_listBuf.size() == 0 && m_iCurPoolSize == m_iPoolSize)
      m_oCondVar.wait(lock);

    lynCodecBuf_t *pFrame = nullptr;
    if (m_listBuf.size() == 0) {
      ++m_iCurPoolSize;
      pFrame = new lynCodecBuf_t();
      pFrame->size = m_iElementSize;
      pFrame->eos = false;
      CHECK_ERR(lynMalloc((void **)&(pFrame->data), m_iElementSize));
    } else {
      pFrame = m_listBuf.front();
      m_listBuf.pop_front();
    }

    m_listBufUsed.push_back(pFrame);
    // std::cout << (uint64_t)this << "FramePool pop " << ++m_iPopCount <<
    // std::endl;
    return pFrame;
  }

  // 还对象，使用frame的data指针
  void Push(void *pData) {
    std::unique_lock<std::mutex> lock(m_oMutex);

    for (auto it = m_listBufUsed.begin(); it != m_listBufUsed.end(); it++) {
      lynCodecBuf_t *pFrame = *it;
      if (pData == pFrame || pData == pFrame->data) {
        // 还对象时释放从前旧分辨率的数据，确保数据被使用完
        if (pFrame->size != m_iElementSize) {
          std::cout << "FramePool Push " << pFrame->size << std::endl;
          CHECK_ERR(lynFree(pFrame->data));
          delete pFrame;
          m_listBufUsed.erase(it);
          return;
        }

        m_listBuf.push_back(pFrame);
        m_listBufUsed.erase(it);
        m_oCondVar.notify_one();
        //  std::cout << (uint64_t)this << "FramePool Push " << ++m_iPushCount
        //  << std::endl;
        return;
      }
    }

    std::cout << "FramePool::Push is not find!!!" << std::endl;
  }

  void ResetSize(uint64_t iElemSize) {
    // 释放空间，保证重新申请空间
    std::unique_lock<std::mutex> lock(m_oMutex);

    if (m_iElementSize != iElemSize) {
      // 释放没有pop出去的内存
      for (lynCodecBuf_t *pData : m_listBuf) {
        CHECK_ERR(lynFree(pData->data));
        delete (pData);
      }
      // 清空m_listBuf，保证pop对象时，重新申请内存
      m_listBuf.clear();
      m_iElementSize = iElemSize;
      m_iCurPoolSize = 0;
    }
  }

  // 释放空间
  void Release() {
    std::unique_lock<std::mutex> lock(m_oMutex);
    if (!m_oCondVar.wait_for(lock, std::chrono::seconds(10), [this] {
          return m_listBuf.size() == m_iCurPoolSize;
        })) {
      std::cout << "[Warning]: m_listBuf is not full, some element may lose "
                   "somewhere outside."
                << std::endl;
    }
    for (lynCodecBuf_t *pData : m_listBuf) {
      CHECK_ERR(lynFree(pData->data));
      delete (pData);
    }
    m_listBuf.clear();
  }

 protected:
  std::mutex m_oMutex;
  std::condition_variable m_oCondVar;
  uint64_t m_iPoolSize;     // 内存池大小
  uint64_t m_iCurPoolSize;  // 当前内存池大小
  uint64_t m_iElementSize;  // 元素大小
  uint64_t m_iPopCount;
  uint64_t m_iPushCount;
  std::list<lynCodecBuf_t *> m_listBuf;      // 可用对象列表
  std::list<lynCodecBuf_t *> m_listBufUsed;  // 已经使用对象列表
};
